/*!	 valuenode_boneinfluence.cpp
**	 Implementation of the "BoneInfluence" valuenode conversion.
**
**	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
**	Copyright (c) 2008 Chris Moore
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**
*/

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include "valuenode_boneinfluence.h"
#include "valuenode_boneweightpair.h"
#include "valuenode_staticlist.h"
#include "valuenode_const.h"
#include "valuenode_composite.h"
#include <synfig/boneweightpair.h>
#include <synfig/canvas.h>
#include <synfig/general.h>
#include <synfig/localization.h>
#include <synfig/valuenode_registry.h>
#include <synfig/blinepoint.h>

#endif

using namespace std;
using namespace etl;
using namespace synfig;

#define epsilon 1e-6

REGISTER_VALUENODE(ValueNode_BoneInfluence, RELEASE_VERSION_0_62_00, "boneinfluence", "Bone Influence")

ValueNode_BoneInfluence::ValueNode_BoneInfluence(Type &x):
    LinkableValueNode(x),
    checked_inverse_(),
    has_inverse_()
{
}

ValueNode_BoneInfluence::ValueNode_BoneInfluence(const ValueNode::Handle &x, Canvas::LooseHandle canvas):
    LinkableValueNode(x->get_type()),
    checked_inverse_(),
    has_inverse_()
{
    Type &type(x->get_type());

    if (type == type_vector || type == type_bline_point) {
        ValueNode_StaticList::Handle bone_weight_list(ValueNode_StaticList::create_on_canvas(type_bone_weight_pair, canvas));
        bone_weight_list->add(ValueNode_BoneWeightPair::create(BoneWeightPair(Bone(), 1), canvas));
        set_link("bone_weight_list",	bone_weight_list);
        set_link("link",				x);

        if (getenv("SYNFIG_DEBUG_SET_PARENT_CANVAS")) {
            printf("%s:%d set parent canvas for bone influence to %lx\n", __FILE__, __LINE__, uintptr_t(canvas.get()));
        }

        set_parent_canvas(canvas);
    } else {
        throw Exception::BadType(type.description.local_name);
    }
}

ValueNode_BoneInfluence*
ValueNode_BoneInfluence::create(const ValueBase &x, Canvas::LooseHandle canvas)
{
    if (x.get_type() == type_bline_point) {
        return new ValueNode_BoneInfluence(ValueNode_Composite::create(x, canvas), canvas);
    }

    return new ValueNode_BoneInfluence(ValueNode_Const::create(x, canvas), canvas);
}

LinkableValueNode*
ValueNode_BoneInfluence::create_new()const
{
    return new ValueNode_BoneInfluence(get_type());
}

ValueNode_BoneInfluence::~ValueNode_BoneInfluence()
{
    unlink_all();
}

ValueBase
ValueNode_BoneInfluence::operator()(Time t)const
{
    if (getenv("SYNFIG_DEBUG_VALUENODE_OPERATORS")) {
        printf("%s:%d operator()\n", __FILE__, __LINE__);
    }

    Matrix transform(get_transform(true, t));
    Type &type(link_->get_type());

    if (type == type_vector) {
        Vector link((*link_)(t).get(Vector()));

        if (getenv("SYNFIG_DEBUG_BONE_VECTOR_TRANSFORMATION"))
            printf("%s\n", transform.get_string(35,
                                                strprintf("transform (%7.2f %7.2f) using",
                                                        link[0],
                                                        link[1]),
                                                strprintf("= (%7.2f %7.2f)",
                                                        transform.get_transformed(link)[0],
                                                        transform.get_transformed(link)[1])).c_str());

        return transform.get_transformed(link);
    }

    if (type == type_bline_point) {
        BLinePoint link((*link_)(t).get(BLinePoint()));
        Point v(link.get_vertex());
        Point vt(transform.get_transformed(v));
        link.set_vertex(vt);

        if (!getenv("SYNFIG_COMPLEX_TANGENT_BONE_INFLUENCE")) {
            link.set_tangent1(transform.get_transformed(link.get_tangent1() + v) - vt);

            if (link.get_split_tangent_both()) {
                link.set_tangent2(transform.get_transformed(link.get_tangent2() + v) - vt);
            }
        } else {
            link.set_boned_vertex_flag(true);
            link.set_vertex_setup(v);
        }

        if (getenv("SYNFIG_DEBUG_BONE_BLINEPOINT_TRANSFORMATION"))
            printf("%s\n", transform.get_string(35,
                                                strprintf("transform v(%7.2f %7.2f) using",
                                                        v[0],
                                                        v[1]),
                                                strprintf("= (%7.2f %7.2f)",
                                                        vt[0],
                                                        vt[1]
                                                         )).c_str());

        return link;
    }

    assert(0);
    return ValueBase();
}

bool
ValueNode_BoneInfluence::set_link_vfunc(int i, ValueNode::Handle value)
{
    assert(i >= 0 && i < link_count());

    switch (i) {
    case 0:
        CHECK_TYPE_AND_SET_VALUE(bone_weight_list_,	type_list);

    case 1:
        CHECK_TYPE_AND_SET_VALUE(link_,				get_type());
    }

    return false;
}

ValueNode::LooseHandle
ValueNode_BoneInfluence::get_link_vfunc(int i)const
{
    assert(i >= 0 && i < link_count());

    switch (i) {
    case 0:
        return bone_weight_list_;

    case 1:
        return link_;
    }

    return 0;
}

bool
ValueNode_BoneInfluence::check_type(Type &type)
{
    return 	type == type_vector ||
            type == type_bline_point;
}

LinkableValueNode::Vocab
ValueNode_BoneInfluence::get_children_vocab_vfunc() const
{
    LinkableValueNode::Vocab ret;

    ret.push_back(ParamDesc(ValueBase(), "bone_weight_list")
                  .set_local_name(_("Bone Weight List"))
                  .set_description(_("List of bones used to calculate the influence"))
                 );

    ret.push_back(ParamDesc(ValueBase(), "link")
                  .set_local_name(_("Link"))
                  .set_description(_("The value node being bone influenced"))
                 );

    return ret;
}

Matrix
ValueNode_BoneInfluence::calculate_transform(Time t)const
{
    Matrix transform;
    transform *= 0;
    vector<ValueBase> bone_weight_list((*bone_weight_list_)(t).get_list());
    Real total_weight = 0;

    for (vector<ValueBase>::iterator iter = bone_weight_list.begin(); iter != bone_weight_list.end(); iter++) {
        Bone bone(iter->get(BoneWeightPair()).get_bone());
        Real weight(iter->get(BoneWeightPair()).get_weight());

        if (getenv("SYNFIG_DEBUG_BONE_TRANSFORM_WEIGHTING")) {
            printf("%s  *\n", Matrix().set_scale(bone.get_local_scale()).get_string(15, "local scale").c_str());
            printf("%s  =\n", bone.get_animated_matrix().get_string(15, "animated", strprintf("* %.2f (weight)", weight)).c_str());
        }

        transform += (Matrix().set_scale(bone.get_local_scale()) *
                      bone.get_animated_matrix() *
                      weight);
        total_weight += weight;
    }

    if (getenv("SYNFIG_DEBUG_BONE_TRANSFORM_WEIGHTING")) {
        printf("%s:%d transform:\n%s\n", __FILE__, __LINE__, transform.get_string().c_str());
        printf("%s:%d total_weight: %.2f\n", __FILE__, __LINE__, total_weight);;
    }

    if (abs(total_weight) > epsilon) {
        transform *= (1 / total_weight);
    } else {
        transform = Matrix();
    }

    if (getenv("SYNFIG_DEBUG_BONE_TRANSFORM_WEIGHTING")) {
        printf("%s:%d final transform:\n%s\n", __FILE__, __LINE__, transform.get_string().c_str());
    }

    return transform;
}

Matrix&
ValueNode_BoneInfluence::get_transform(bool rebuild, Time t)const
{
    if (rebuild) {
        set_transform(calculate_transform(t));
    }

    return transform_;
}

bool
ValueNode_BoneInfluence::has_inverse_transform()const
{
    if (checked_inverse_) {
//		printf("%s:%d returning stored value %d for has_inverse\n", __FILE__, __LINE__, has_inverse_);
        return has_inverse_;
    }

    inverse_transform_ = get_transform();

    if ((has_inverse_ = inverse_transform_.is_invertible())) {
        inverse_transform_.invert();
    }

//	printf("%s:%d returning calculated value %d for has_inverse\n", __FILE__, __LINE__, has_inverse_);

    checked_inverse_ = true;
    return has_inverse_;
}

Matrix&
ValueNode_BoneInfluence::get_inverse_transform()const
{
    if (has_inverse_transform()) {
        return inverse_transform_;
    }

    error("get_inverse_transform() called when no inverse is available");
    assert(0);
    return inverse_transform_;
}